package com.itita.ww2.redis.core;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.itita.ww2.redis.cache.Hash;
import com.itita.ww2.redis.cache.Table;
import com.lambdaworks.redis.ClientOptions;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.api.sync.RedisCommands;
import com.smartfoxserver.v2.util.TaskScheduler;

public class Redis {
	private static final Logger logger = LoggerFactory.getLogger(Redis.class);
	// fields
	private Connection conn;
	// private TaskScheduler scheduler;
	private RedisConfig config;

	// lettuce
	private RedisClient redisClient;
	private StatefulRedisConnection<String, String> redisConn;
	private RedisCommands<String, String> redisCmd;
	// private GenericObjectPool<StatefulRedisConnection<String, String>> redisConnPool;

	// table
	private ConcurrentHashMap<String, Table> m_Tables = new ConcurrentHashMap<>();
	private ConcurrentHashMap<String, Hash> m_Hashs = new ConcurrentHashMap<>();

	private Lock lock = new ReentrantLock();

	public Redis(TaskScheduler scheduler, RedisConfig redisConfig) {
		// this.scheduler = scheduler;
		this.config = redisConfig;
		// 
		RedisURI uri = new RedisURI();
		uri.setHost(config.getMasterHost());
		uri.setPort(config.getMasterPort());
		uri.setPassword(config.getMasterPwd());
//		// @formatter:off
		ClientOptions opt = ClientOptions.builder()
				.autoReconnect(true)
				.pingBeforeActivateConnection(true)
				.suspendReconnectOnProtocolFailure(false)
				.build();
//		// @formatter:on
		redisClient = RedisClient.create(uri);
		redisClient.setOptions(opt);

		redisConn = redisClient.connect(uri);
		redisCmd = redisConn.sync();
		// 
		//		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
		//		poolConfig.setJmxEnabled(config.isJmxEnable());
		//		poolConfig.setMaxIdle(config.getPoolMaxIdle());
		//		poolConfig.setMaxTotal(config.getPoolMaxTotal());
		//		poolConfig.setMaxWaitMillis(config.getPoolMaxWaitMillis());
		//		poolConfig.setTestOnBorrow(config.isPoolTestOnBorrow());
		//		poolConfig.setTestOnReturn(config.isPoolTestOnReturn());

		// redisConnPool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(), poolConfig,true);

		startTask(config.getUpdateInterval());

		logger.info(Redis.class.getSimpleName() + " initialized");
	}

	/**
	 * landing task
	 */
	private void startTask(int updateInterval) {
		Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
			@Override
			public Thread newThread(Runnable r) {
				Thread t = new Thread(r);
				// t.setDaemon(true);
				t.setName("Redis:Thread");
				return t;
			}
		}).scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				syncToDB(false);
			}
		}, updateInterval, updateInterval, TimeUnit.SECONDS);
		logger.info("Redis Task Start...Interval:{}s", updateInterval);
	}

	/**
	 * Get Database Connection
	 * @return
	 */
	public Connection getDBConnection() {
		try {
			lock.lock();
			if (conn != null) {
				if (conn.isValid(5)) {
					return conn;
				}
			}
			if (conn == null) {
				Class.forName(config.getDbDriverName());
			}
			conn = DriverManager.getConnection(config.getDbConnectionUrl(), config.getDbUsername(),
					config.getDbPassword());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return this.conn;
	}

	public Connection getLandingDBConnection() {
		Connection ref = null;
		try {
			lock.lock();
			Class.forName(config.getDbDriverName());
			ref = DriverManager.getConnection(config.getDbConnectionUrl(), config.getDbUsername(),
					config.getDbPassword());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return ref;
	}

	/**
	 * flush all redis data
	 */
	public void flushAll() {
		RedisCommands<String, String> cmd = redisConn.sync();
		cmd.flushall();
		m_Tables.clear();
		m_Hashs.clear();
	}

	/**
	 * landing
	 */
	public void syncToDB(boolean force) {
		for (Table table : m_Tables.values()) {
			try {
				table.landing(force);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public Table setTable(String tableName, String[] keys, boolean lasyLoad, boolean landing) {
		if (!this.m_Tables.containsKey(tableName)) {
			Table val = new Table(this, tableName, keys, lasyLoad, landing);
			this.m_Tables.put(tableName, val);
			return val;
		} else {
			return this.m_Tables.get(tableName);
		}
	}

	public Table getTable(String tableName) {
		Table table = m_Tables.get(tableName);
		if (table == null) {
			logger.warn("Table [" + tableName + "] not set.");
		}
		return table;
	}

	public Hash setHash(String keyName) {
		if (!this.m_Hashs.containsKey(keyName)) {
			Hash val = new Hash(this, keyName);
			this.m_Hashs.put(keyName, val);
			return val;
		} else {
			return this.m_Hashs.get(keyName);
		}
	}

	public Hash getHash(String keyName) {
		Hash ref = m_Hashs.get(keyName);
		if (ref == null) {
			logger.warn("Hash [" + keyName + "] not set.");
		}
		return ref;
	}

	//	public GenericObjectPool<StatefulRedisConnection<String, String>> getPool() {
	//		return redisConnPool;
	//	}

	//	public StatefulRedisConnection<String, String> getRedisConn() {
	//		return this.redisConn;
	//	}

	public RedisCommands<String, String> getRedisCmd() {
		try {
			lock.lock();
			return this.redisCmd;
		} finally {
			lock.unlock();
		}
	}

	public RedisConfig getConfig() {
		return config;
	}

	//	public void returnResource(StatefulRedisConnection<String, String> resource) {
	//		this.redisConnPool.returnObject(resource);
	//	}
}
